
package com.jf.cloud.auth.controller;

import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.hutool.core.util.StrUtil;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import com.jf.cloud.api.auth.bo.UserInfoInTokenBO;
import com.jf.cloud.api.auth.constant.LoginType;
import com.jf.cloud.api.auth.constant.SocialType;
import com.jf.cloud.api.auth.constant.SysTypeEnum;
import com.jf.cloud.api.auth.vo.TokenInfoVO;
import com.jf.cloud.api.biz.feign.NotifyFeignClient;
import com.jf.cloud.api.rbac.dto.ClearUserPermissionsCacheDTO;
import com.jf.cloud.api.rbac.feign.PermissionFeignClient;
import com.jf.cloud.api.user.bo.UidAndUserIdBO;
import com.jf.cloud.api.user.dto.UserRegisterDTO;
import com.jf.cloud.api.user.feign.UserFeignClient;
import com.jf.cloud.auth.config.WxConfig;
import com.jf.cloud.auth.dto.*;
import com.jf.cloud.auth.manager.TokenStore;
import com.jf.cloud.auth.mapper.AuthSocialMapper;
import com.jf.cloud.auth.model.AuthSocial;
import com.jf.cloud.auth.service.AuthAccountService;
import com.jf.cloud.auth.service.AuthSocialService;
import com.jf.cloud.common.constant.SendTypeEnum;
import com.jf.cloud.common.exception.LuckException;
import com.jf.cloud.common.response.ResponseEnum;
import com.jf.cloud.common.response.ServerResponseEntity;
import com.jf.cloud.common.security.AuthUserContext;
import com.jf.cloud.common.security.adapter.AuthConfigAdapter;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.Objects;

/**
 * @author zz
 * @date 2020/6/30
 */
@RestController
@Tag(name = "登录")
public class LoginController {

    @Autowired
    private CaptchaService captchaService;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private AuthAccountService authAccountService;

    @Autowired
    private PermissionFeignClient permissionFeignClient;

    @Autowired
    private AuthSocialService authSocialService;

    @Autowired
    private WxConfig wxConfig;

    @Autowired
    private NotifyFeignClient notifyFeignClient;

    @Autowired
    private AuthSocialMapper authSocialMapper;

    @Autowired
    private UserFeignClient userFeignClient;

    @PostMapping("/ua/login")
    @Operation(summary = "账号密码(用于h5、pc登录)" , description = "通过账号/手机号/用户名密码登录，还要携带用户的类型，也就是用户所在的系统")
    public ServerResponseEntity<TokenInfoVO> login(
            @Valid @RequestBody AuthenticationDTO authenticationDTO) {
        if (!Objects.equals(authenticationDTO.getSysType(), SysTypeEnum.ORDINARY.value())) {
            return ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED);
        }
        return doLogin(authenticationDTO, LoginType.PASSWORD.value());
    }

    @PostMapping("/ua/login_with_code")
    @Operation(summary = "手机号+验证码登录(用于前端登录)" , description = "通过手机号+验证码登录，还要携带用户的类型，也就是用户所在的系统")
    public ServerResponseEntity<TokenInfoVO> loginWithCode(@Valid @RequestBody LoginCodeDTO loginCode) {
        // 该接口是用户端在（pc、h5、小程序）使用
        if (!Objects.equals(loginCode.getSysType(), SysTypeEnum.ORDINARY.value())) {
            return ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED);
        }
        boolean flag = (Objects.equals(loginCode.getSocialType(), SocialType.MA.value())
                || Objects.equals(loginCode.getSocialType(), SocialType.MP.value()));
        if (StrUtil.isBlank(loginCode.getTempUid())  && flag) {
            return ServerResponseEntity.showFailMsg("tempUid is blank");
        }
        //校验验证码
        ServerResponseEntity<Boolean> validCodeResponse = notifyFeignClient.checkValidCode(loginCode.getMobile(), loginCode.getValidCode(), SendTypeEnum.LOGIN);
        if (!validCodeResponse.isSuccess() || !validCodeResponse.getData()) {
            return ServerResponseEntity.fail(ResponseEnum.VERIFICATION_CODE_ERROR);
        }
        // 如果没有注册的话，短信登录将会进行注册
        // 在pc/小程序/公众号的登录，都有短信登录的方法。但是公众号/小程序的短信登录，登录了之后会见这个用户和公众号/小程序绑定一起（登录并绑定）
        return threeInOneLogin(loginCode.getMobile(), loginCode.getTempUid(), loginCode.getSocialType());
    }

    @PostMapping("/ua/wx/login")
    @Operation(summary = "账号密码登录(用于微信小程序和公众号)" , description = "二合一登录（包含绑定+登录），一定要传tempUid")
    public ServerResponseEntity<TokenInfoVO> tempSocialLogin(
            @Valid @RequestBody AuthenticationDTO authenticationDTO) {
        if (!Objects.equals(authenticationDTO.getSysType(), SysTypeEnum.ORDINARY.value())) {
            return ServerResponseEntity.showFailMsg("sysType error");
        }
        if (StrUtil.isBlank(authenticationDTO.getTempUid())) {
            return ServerResponseEntity.showFailMsg("tempUid is blank");
        }
        return doLogin(authenticationDTO, LoginType.PASSWORD.value());
    }

    @PostMapping("/ua/ma/login")
    @Operation(summary = "微信小程序一键登录" , description = "三合一登录（包含注册 + 绑定 + 登录）")
    public ServerResponseEntity<TokenInfoVO> maLogin(@Valid @RequestBody MaCodeAuthenticationDTO maCodeAuthenticationDTO) {
        WxMaPhoneNumberInfo newPhoneNoInfo;
        try {
            newPhoneNoInfo = wxConfig.getWxMaService().getUserService().getPhoneNoInfo(maCodeAuthenticationDTO.getCode());
        } catch (WxErrorException e) {
            throw new LuckException(e.getMessage());
        }
        // 没有区号的手机号，国外手机号会有区号
        String mobile = newPhoneNoInfo.getPurePhoneNumber();

        return threeInOneLogin(mobile, maCodeAuthenticationDTO.getTempUid(), SocialType.MA.value());
    }

    @PostMapping("/ua/admin_login")
    @Operation(summary = "账号密码 + 验证码登录(用于后台登录)" , description = "通过账号/手机号/用户名密码登录，还要携带用户的类型，也就是用户所在的系统")
    public ServerResponseEntity<TokenInfoVO> login(
            @Valid @RequestBody CaptchaAuthenticationDTO captchaAuthenticationDTO) {
        // 登陆后台登录需要再校验一遍验证码
        CaptchaVO captchaVO = new CaptchaVO();
        captchaVO.setCaptchaVerification(captchaAuthenticationDTO.getCaptchaVerification());
        ResponseModel response = captchaService.verification(captchaVO);
        if (!response.isSuccess()) {
            return ServerResponseEntity.showFailMsg("验证码已失效，请重新获取");
        }
        return doLogin(captchaAuthenticationDTO, LoginType.PASSWORD.value());
    }


    private ServerResponseEntity<TokenInfoVO> doLogin(@RequestBody @Valid AuthenticationDTO authenticationDTO, Integer loginType) {
        ServerResponseEntity<UserInfoInTokenBO> userInfoInTokenResponse = authAccountService
                .getUserInfoInTokenByInputUserNameAndPassword(authenticationDTO.getUserName(),
                        authenticationDTO.getPassWord(), authenticationDTO.getSysType(), loginType);
        if (!userInfoInTokenResponse.isSuccess()) {
            return ServerResponseEntity.transform(userInfoInTokenResponse);
        }

        UserInfoInTokenBO data = userInfoInTokenResponse.getData();

        ClearUserPermissionsCacheDTO clearUserPermissionsCacheDTO = new ClearUserPermissionsCacheDTO();
        clearUserPermissionsCacheDTO.setSysType(data.getSysType());
        clearUserPermissionsCacheDTO.setUserId(data.getUserId());
        // 将以前的权限清理了,以免权限有缓存
        ServerResponseEntity<Void> clearResponseEntity = permissionFeignClient.clearUserPermissionsCache(clearUserPermissionsCacheDTO);

        if (!clearResponseEntity.isSuccess()) {
            return ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED);
        }

        if (StrUtil.isNotBlank(authenticationDTO.getTempUid())) {
            AuthSocial authSocial = authSocialService.getByTempUid(authenticationDTO.getTempUid());
            if (Objects.isNull(authSocial)) {
                return ServerResponseEntity.fail(ResponseEnum.TEMP_UID_ERROR);
            }
            data.setBizUid(authSocial.getBizUnionid());
            data.setBizUserId(authSocial.getBizUserId());
            data.setSessionKey(authSocial.getBizTempSession());
            data.setSocialType(authSocial.getSocialType());
        }

        // 保存token，返回token数据给前端
        return ServerResponseEntity.success(tokenStore.storeAndGetVo(data));
    }



    @PostMapping(AuthConfigAdapter.LOGIN_OUT_URI)
    @Operation(summary = "退出登陆" , description = "点击退出登陆，清除token，清除菜单缓存")
    public ServerResponseEntity<Void> loginOut(HttpServletRequest request) {
        UserInfoInTokenBO userInfoInToken = AuthUserContext.get();
        if (userInfoInToken == null) {
            return ServerResponseEntity.success();
        }

        ClearUserPermissionsCacheDTO clearUserPermissionsCacheDTO = new ClearUserPermissionsCacheDTO();
        clearUserPermissionsCacheDTO.setSysType(userInfoInToken.getSysType());
        clearUserPermissionsCacheDTO.setUserId(userInfoInToken.getUserId());
        if (!Objects.equals(SysTypeEnum.ORDINARY.value(), userInfoInToken.getSysType())) {
            // 将以前的权限清理了,以免权限有缓存
            permissionFeignClient.clearUserPermissionsCache(clearUserPermissionsCacheDTO);
        }

        // 只有微信公众号和小程序的登录会进行登录并绑定的操作
        if (!Objects.equals(userInfoInToken.getSocialType(), SocialType.MA.value()) && !Objects.equals(userInfoInToken.getSocialType(),SocialType.MP.value())) {
            return ServerResponseEntity.success();
        }
        // 解绑
        authSocialMapper.unBindUser(userInfoInToken.getBizUserId(), userInfoInToken.getSocialType());

        String accessToken = request.getHeader("Authorization");

        // 删除该用户在该系统当前的token
        tokenStore.deleteCurrentToken(userInfoInToken.getSysType().toString(), userInfoInToken.getUid(),accessToken);

        return ServerResponseEntity.success();
    }

    @PutMapping("/checkPhoneIsBind")
    @Operation(summary = "检查用户修改后的手机号是否已经绑定其他账号" , description = "检查用户修改后的手机号是否已经绑定其他账号")
    public ServerResponseEntity<Boolean> checkPhoneIsBind(@RequestBody BindSocialDTO checkSmsDTO) {

        int accountNum = authAccountService.getByMobile(checkSmsDTO.getValidAccount());
        if (accountNum > 0) {
            // 此用户不存在，请先注册
            throw new LuckException("此手机号已经绑定过其他账号，请重新输入。");
        }
        return ServerResponseEntity.success(true);
    }


    /**
     * 三合一登录（包含注册 + 绑定 + 登录）
     */
    private ServerResponseEntity<TokenInfoVO> threeInOneLogin(String mobile,String tempUid, Integer socialType) {
        ServerResponseEntity<UserInfoInTokenBO> userInfoInTokenResponse = authAccountService
                .getUserInfoInTokenByInputUserNameAndPassword(mobile,
                        null, SysTypeEnum.ORDINARY.value(), LoginType.ONE_CLICK_LOGIN.value());
        if (!userInfoInTokenResponse.isSuccess()) {
            return ServerResponseEntity.transform(userInfoInTokenResponse);
        }
        UserInfoInTokenBO userInfoInTokenBO = userInfoInTokenResponse.getData();
        AuthSocial authSocial= null;
        // 没有就注册
        if (userInfoInTokenBO == null || userInfoInTokenBO.getUid() == null) {
            if (Objects.equals(socialType,SocialType.MA.value()) || Objects.equals(socialType,SocialType.MP.value())) {
                authSocial = authSocialService.getByTempUid(tempUid);
                if (authSocial == null || authSocial.getUid() != null) {
                    return ServerResponseEntity.fail(ResponseEnum.TEMP_UID_ERROR);
                }
            }
            UserRegisterDTO userRegisterDTO = new UserRegisterDTO();
            userRegisterDTO.setMobile(mobile);
            userRegisterDTO.setTempUid(tempUid);
            // 注册并绑定用户
            ServerResponseEntity<UidAndUserIdBO> saveResponse = userFeignClient.save(userRegisterDTO);
            if (!saveResponse.isSuccess()) {
                return ServerResponseEntity.transform(saveResponse);
            }
            UidAndUserIdBO uidAndUserIdBO = saveResponse.getData();
            userInfoInTokenBO = new UserInfoInTokenBO();
            userInfoInTokenBO.setUserId(uidAndUserIdBO.getUserId());
            userInfoInTokenBO.setUid(uidAndUserIdBO.getUid());
            userInfoInTokenBO.setIsAdmin(0);
            userInfoInTokenBO.setSysType(SysTypeEnum.ORDINARY.value());
        } else  {
            authSocial = checkAndGetAppConnect(userInfoInTokenBO.getUid(), socialType, tempUid);
        }
        if (authSocial != null) {
            userInfoInTokenBO.setBizUid(authSocial.getBizUnionid());
            userInfoInTokenBO.setBizUserId(authSocial.getBizUserId());
            userInfoInTokenBO.setSessionKey(authSocial.getBizTempSession());
            userInfoInTokenBO.setSocialType(authSocial.getSocialType());
        }
        return ServerResponseEntity.success(tokenStore.storeAndGetVo(userInfoInTokenBO));
    }

    /**
     * 登录并绑定用户
     */
    private AuthSocial checkAndGetAppConnect(Long uid, Integer socialType,String tempUid) {
        // 只有微信公众号和小程序的登录会进行登录并绑定的操作
        if (!Objects.equals(socialType,SocialType.MA.value()) && !Objects.equals(socialType,SocialType.MP.value())) {
            return null;
        }
        // 在SocialLoginController 当中，会返回一个tempUid用来换取openid的
        AuthSocial authSocial = authSocialService.getByTempUid(tempUid);
        if (authSocial == null) {
            throw new LuckException(ResponseEnum.TEMP_UID_ERROR);
        }
        // 二合一登录，啥意思呢？
        // 1. 绑定：将该账号和该微信的openId进行绑定
        // 2. 登录：返回token登录成功
        if (authSocial.getUid() == null) {
            authSocialMapper.bindUidByTempUid(uid, authSocial.getTempUid());
        }
        // 如果有userId就判断下是不是一样的user，不是的话，不能够绑定
        else if (!Objects.equals(authSocial.getUid(), uid)) {
            throw new LuckException(ResponseEnum.SOCIAL_ACCOUNT_BIND_BY_OTHER);
        }
        return authSocial;
    }

}
